﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Xml.Linq;

using Perseus;
using Perseus.Xml;

using Hetymine.Executioner.Commands;
using Hetymine.Executioner.Data;

namespace Hetymine.Executioner.Plugins.Commands.FTP {
    public class FTPCommand : ICommand {
        private static FTPCommandConfigurator _Configurator;

        private bool _IsError;

        public FTPCommand() {
            this.Name = string.Empty;
            this.CallStrings = new List<string>();
            
            this.Host = string.Empty;
            this.Port = 21;
            this.Username = string.Empty;
            this.Password = string.Empty;
            this.Passive = false;

            this.FileDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) +
                    Path.DirectorySeparatorChar;
            this.Copy = false;
            this.CopyUrl = string.Empty;            
        }

        public string Host { get; set; }
        public int Port { get; set; }
        public bool Passive { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }

        public string FileDirectory { get; set; }
        public bool Copy { get; set; }
        public string CopyUrl { get; set; }        

        #region ICommand Members
        public string Name { get; set; }
        public bool IsEmpty {
            get {
                if (this.CallStrings.Count < 1) {
                    return true;
                }

                if (this.Host.IsEmpty()) {
                    return true;
                }

                return false;
            }
        }

        public void Load(XElement element) {
            this.Name = element.Element("Name").ValueOrEmpty();

            var callStrings = from s in element.Elements("CallString")
                              select s;

            foreach (string s in callStrings) {
                this.CallStrings.Add(s);
            }

            this.Host = element.Element("Host").ValueOrEmpty();            
            int port;
            if (!int.TryParse(element.Element("Port").ValueOr("21"), out port)) {
                port = 21;
            }
            this.Port = port;
                
            this.Username = element.Element("Username").ValueOrEmpty();
            this.Password = element.Element("Password").ValueOrEmpty();
            this.Passive = element.Attribute("Passive").BoolOrFalse();

            this.FileDirectory = element.Element("FileDirectory").ValueOrEmpty();
            if (this.FileDirectory.IsEmpty()) {
                this.FileDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) +
                    Path.DirectorySeparatorChar;
            }
            this.Copy = element.Attribute("Copy").BoolOrFalse();
            this.CopyUrl = element.Element("CopyUrl").ValueOrEmpty();
            
        }
        public XElement Save() {
            if (this.IsEmpty) {
                return null;
            }

            XElement xe = new XElement("FTP");
            if (this.Passive) {
                xe.Add(new XAttribute("Passive", "true"));
            }
            if (this.Copy) {
                xe.Add(new XAttribute("Copy", "true"));
            }

            xe.Add(new XElement("Name", this.Name));

            foreach (string cs in this.CallStrings) {
                xe.Add(new XElement("CallString", cs));
            }

            xe.Add(new XElement("Host", this.Host));
            xe.Add(new XElement("Port", this.Port.ToString()));
            if (this.Username != string.Empty) {
                xe.Add(new XElement("Username", this.Username));
                xe.Add(new XElement("Password", this.Password));
            }

            if (this.FileDirectory != string.Empty) {
                xe.Add(new XElement("FileDirectory", this.FileDirectory));
            }
            if (this.CopyUrl != string.Empty) {
                xe.Add(new XElement("CopyUrl", this.CopyUrl));
            }

            return xe;
        }

        public List<string> CallStrings { get; set; }
        public ExecutionResult Execute(string parameters) {
            this._IsError = false;

            string file = this.FileName(parameters);
            if (file.IsEmpty()) {
                // Internal error so not users fault
                if (this._IsError) {
                    return ExecutionResult.Error;
                }
                return ExecutionResult.Failure;
            }

            string host = this.RequestUri(file);
            FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(host);            
            request.Method = WebRequestMethods.Ftp.UploadFile;
            request.Credentials = new NetworkCredential(this.Username, this.Password);
            request.UsePassive = this.Passive;
            request.UseBinary = true;
            request.KeepAlive = false;

            int bytes;
            byte[] buffer = new byte[512];

            FileStream fileStream = new FileStream(file, FileMode.Open);
            Stream requestStream = request.GetRequestStream();

            while ((bytes = fileStream.Read(buffer, 0, buffer.Length)) > 0) {
                requestStream.Write(buffer, 0, bytes);
            }

            requestStream.Close();
            fileStream.Close();

            if (this.Copy) {
                System.Windows.Clipboard.SetText(this.FileUrl(file));
            }

            return ExecutionResult.Success;
        }
        /// <summary>
        /// Gets the file to be uploaded. Depending on the specified parameters 
        /// the file might be generated on the fly. An example of this would be clipboard data.
        /// </summary>
        /// <param name="parameters">The parameters sent to this command.</param>
        /// <returns>The file to be uploaded.</returns>
        private string FileName(string parameters) {
            string file;

            var prams = new ParameterInfo(parameters);

            if (prams.Flags.ContainsKey("c")) {
                if (Clipboard.ContainsImage()) {
                    return this.ClipboardImageFile(prams);
                }
                else if (Clipboard.ContainsText()) {
                    return this.ClipboardTextFile(prams);
                }
                else { // Unknown type
                    return string.Empty;
                }
            }
            
            if (Path.IsPathRooted(parameters)) {
                file = parameters;
            }
            else { // Use default directory if path is not rooted
                file = this.FileDirectory + parameters;
            }

            if (!File.Exists(file)) {
                return string.Empty;
            }

            return file;
        }
        private string ClipboardImageFile(ParameterInfo prams) {
            string cname;

            if (prams.Count > 0) {
                cname = prams.Parameters[0];
            }
            else {
                cname = Executioner.Config.GetString("FTP", "Default Image Filename", "clipboard");
            }

            string file = Executioner.PluginDirectory +
                "FTP" + Path.DirectorySeparatorChar +
                cname + ".png";

            string tmpFile = Path.GetTempFileName();
            FileStream fs = new FileStream(tmpFile, FileMode.Create, FileAccess.Write);
            try {
                BitmapSource image = Clipboard.GetImage();
                BmpBitmapEncoder bbe = new BmpBitmapEncoder();
                bbe.Frames.Add(BitmapFrame.Create(image));
                bbe.Save(fs);
                fs.Close();

                Uri uri = new Uri(tmpFile);
                using (fs = new FileStream(file, FileMode.Create, FileAccess.Write)) {
                    PngBitmapEncoder pbe = new PngBitmapEncoder();
                    pbe.Interlace = PngInterlaceOption.Off;
                    pbe.Frames.Add(BitmapFrame.Create(uri));
                    pbe.Save(fs);
                }
            }
            catch {
                App.Log("FTP: Failed to create clipboard image.");
                this._IsError = true;
                Executioner.Sounds.Error();
                fs.Close();
                return string.Empty;
            }

            return file;
        }
        private string ClipboardTextFile(ParameterInfo prams) {
            string cname;

            if (prams.Count > 0) {
                cname = prams.Parameters[0];
            }
            else {
                cname = Executioner.Config.GetString("FTP", "Default Text Filename", "clipboard");
            }

            string file = Executioner.PluginDirectory +
                "FTP" + Path.DirectorySeparatorChar +
                cname + ".txt";

            StreamWriter sw = new StreamWriter(file);
            try {
                sw.Write(Clipboard.GetText());
                sw.Close();
            }
            catch {
                App.Log("FTP: Failed to create clipboard text file.");
                this._IsError = true;
                Executioner.Sounds.Error();
                sw.Close();
                return string.Empty;
            }

            return file;
        }
        /// <summary>
        /// Gets the full file name url of the ftp server to upload the file to.
        /// </summary>
        /// <param name="file">The file being uploaded.</param>
        /// <returns>FTP server file name url.</returns>
        private string RequestUri(string file) {
            string uri = string.Empty;

            // If no protocal specified, default to ftp
            if (this.Host.IndexOf("://") < 0) {
                uri = "ftp://";
            }

            // If directory is specified, insert port before it
            int portPos = this.Host.IndexOf("/");
            if (portPos > 0) {
                uri += this.Host.Substring(0, portPos);
                uri += ":" + this.Port;
                uri += this.Host.Substring(portPos);
            }
            else {
                uri += this.Host + ":" + this.Port;
            }

            if (!uri.EndsWith("/", StringComparison.Ordinal)) {
                uri += "/";
            }

            uri += Path.GetFileName(file);

            return uri;
        }
        private string FileUrl (string file) {            
            string url = string.Empty;

            if (this.CopyUrl.IsEmpty()) {                    
                string host = this.Host;

                int portPos = this.Host.IndexOf("/");
                if (portPos > 0) {
                    url = this.Host.Substring(0, portPos);
                    // Skip over one directory since most of the time there 
                    // is a public_html folder or equivalent
                    portPos = this.Host.IndexOf("/", portPos);
                    if (portPos > 0) {
                        url = this.Host.Substring(portPos);
                    }
                }
                else {
                    url = this.Host;
                }
            }
            else {
                url = this.CopyUrl;
            }

            // If no protocal specified, default to ftp
            if (this.Host.IndexOf("://") < 0) {
                url = "http://" + url;
            }

            if (!url.EndsWith("/", StringComparison.Ordinal)) {
                url += "/";
            }

            url += Path.GetFileName(file);

            return url;
        }

        public ICommandConfigurator<ICommand> Configurator {
            get {
                if (FTPCommand._Configurator == null) {
                    FTPCommand._Configurator = new FTPCommandConfigurator();
                }
                FTPCommand._Configurator.InitializeConfig(this);
                return FTPCommand._Configurator;
            }
        }

        public object Clone() {
            FTPCommand ftpc = (FTPCommand)this.MemberwiseClone();

            ftpc.CallStrings = new List<string>();
            foreach (string cs in this.CallStrings) {
                ftpc.CallStrings.Add(cs);
            }

            return ftpc;
        }
        #endregion
    }
}
